package glemipsc.instructions;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import glemipsc.*;
import glemipsc.implementations.Argument;
import glemipsc.implementations.Implementation;

public class VariableRef extends BaseInstruction implements Serializable, Instruction, Expression {
	private SymbolElement op;
	private ArrayList<Expression> indexes = new ArrayList<Expression>();
	private Address addr;
	
	public VariableRef(SymbolElement op) {
		this.op = op;
	}
	
	public void addIndex(Expression exp) {
		indexes.add(exp);
	}
	
	public Expression getOperand(int index) {
		if (index == 0)
			return op;
		return null;
	}
	
	public int getNumberOfOperands() {
		return 1;
	}

	public int getSize() {
		return 0;
	}
	
	public Type getType() {
		Type type = op.getType();
		for (int i = 0; i < indexes.size(); i++) {
			type = type.getNestedType();
		}
		return type;
	}

	public int getImmediateValue() {
		return 0;
	}

	public boolean isImmediate() {
		return false;
	}

	public Address getStoreAddress() {
		return addr;
	}

	public int getStoredSize() {
		// TODO Auto-generated method stub
		return 0;
	}

	public boolean isStored() {
		return true;
	}

	public void setStoreAddress(Address addr) {
		this.addr = addr;
	}
	
	public Address getVariableAddress() {
		// TODO: Calculate pointed variable address
		return op.getStoreAddress();
	}
	
	public int getVariableSize() {
		// TODO: Calculate pointed variable size
		Iterator<Expression> iter = indexes.iterator();
		Type tmp = op.getType();
		while (tmp.getNestedType() != null && iter.hasNext()) {
			iter.next();
			tmp = tmp.getNestedType();
		}
		return tmp.getValueLength();
	}
	
	public boolean isConstantPointer() {
		Iterator<Expression> iter = indexes.iterator();
		Type tmpType = op.getType();
		while (tmpType.getNestedType() != null && iter.hasNext()) {
			tmpType = tmpType.getNestedType();
			iter.next();
		}
		while (tmpType.getNestedType() != null) {
			if (tmpType.getLength() > 1)
				return true;
		}
		return false;
	}

	public void computeInstruction(Argument result, List<glemipsc.implementations.Instruction> instructions, Function func) {
		Implementation impl = GLEMipsC.compiler.getEnvironment().implementation;
		Argument addr = null;
		
		if (!isConstantPointer())
			addr = impl.getFreeRegister();
		Address valueAddr = computeAddress(isConstantPointer() ? result : addr, instructions, func);
		if (!isConstantPointer()) {
			instructions.add(impl.getLd(result, valueAddr, getVariableSize()));
			impl.freeRegister(addr);
		}
	}
	
	public Address computeAddress(Argument result, List<glemipsc.implementations.Instruction> instructions, Function func) {
		Implementation impl = GLEMipsC.compiler.getEnvironment().implementation;
		
		if (op.getType().getTypeFormat() != Type.TypeFormat.ARRAY) {
			return op.getStoreAddress();
		}
		
		Address tmp = op.getStoreAddress();
		instructions.add(impl.getAdd(result, tmp.getSegment(), tmp.getOffset(), false));
		Argument reg = impl.getFreeRegister();
		Type tmpType = op.getType().getNestedType();
		Iterator<Expression> iter = indexes.iterator();
		while (tmpType != null && iter.hasNext()) {
			Expression expr = iter.next();
			expr.computeInstruction(reg, instructions, func);
			if (tmpType.sizeOf()/4 != 1)	// Risparmio una moltiplicazione se la dimensione è 1
				instructions.add(impl.getMul(reg, reg, impl.getImmediate(tmpType.sizeOf()/4), false));
			instructions.add(impl.getAdd(result, result, reg, false));
			tmpType = tmpType.getNestedType();
		}
		impl.freeRegister(reg);
		return new RelativeAddress(result, 0);
	}

}
